#include "i2c.h"

#define MODE_INPUT                        0
#define MODE_OUTPUT_SPEED_10MHZ           1
#define MODE_OUTPUT_SPEED_2MHZ            2
#define MODE_OUTPUT_SPEED_50MHZ           3

#define CONF_OUTPUT_GENERAL_PUSH_PULL     0
#define CONF_OUTPUT_GENERAL_OPEN_DRAIN    1
#define CONF_OUTPUT_ALTERNATE_PUSH_PULL   2
#define CONF_OUTPUT_ALTERNATE_OPEN_DRAIN  3

#define CONF_INPUT_ANALOG                 0
#define CONF_INPUT_FLOATING               1
#define CONF_INPUT_PULL_UP_DOWN           2
#define CONF_INPUT_RESERVED               3

// 初始化
void I2C_Init(void)
{
    // 1. 配置时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;

    // 2. GPIO工作模式配置：复用开漏输出 CNF-11，MODE-11
    // PB10配置为开漏输出
    GPIOB->CRH &= ~(0x0F << (2 * 4));
    GPIOB->CRH |= MODE_OUTPUT_SPEED_50MHZ          << (2 * 4);
    GPIOB->CRH |= CONF_OUTPUT_ALTERNATE_OPEN_DRAIN << (2 * 4 + 2);

    // PB11配置为开漏输出
    GPIOB->CRH &= ~(0x0F << (3 * 4));
    GPIOB->CRH |= MODE_OUTPUT_SPEED_50MHZ          << (3 * 4);
    GPIOB->CRH |= CONF_OUTPUT_ALTERNATE_OPEN_DRAIN << (3 * 4 + 2);

    // 3. I2C2配置
    // 3.1 硬件工作模式
    I2C2->CR1 &= ~I2C_CR1_SMBUS;
    I2C2->CCR &= ~I2C_CCR_FS;

    // 3.2 选择输入的时钟频率
    I2C2->CR2 |= 36;

    // 3.3 配置CCR，对应数据传输速率100kb/s，SCL高电平时间为 5us
    I2C2->CCR |= 180;

    // 3.4 配置TRISE，SCL上升沿最大时钟周期数 + 1
    I2C2->TRISE |= 37;

    // 3.5 使能I2C2模块
    I2C2->CR1 |= I2C_CR1_PE;
}

// 发出起始信号
uint8_t I2C_Start(void)
{
    // 产生一个起始信号
    I2C2->CR1 |= I2C_CR1_START;

    // 引入一个超时时间
    uint16_t timeout = 0xffff;

    // 等待起始信号发出
    while ((I2C2->SR1 & I2C_SR1_SB) == 0 && timeout)
    {
        timeout--;
    }
    return timeout ? OK : FAIL;
}

// 设置接收完成之后发出停止信号
void I2C_Stop(void)
{
    I2C2->CR1 |= I2C_CR1_STOP;
}

// 主机设置使能应答信号
void I2C_Ack(void)
{
    I2C2->CR1 |= I2C_CR1_ACK;
}

// 主机设置使能非应答信号
void I2C_Nack(void)
{
    I2C2->CR1 &= ~I2C_CR1_ACK;
}

// 主机发送设备地址，并等待应答
uint8_t I2C_SendAddr(uint8_t addr)
{
    // 直接将要发送的地址给到DR
    I2C2->DR = addr;

    // 等待应答
    uint16_t timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout)
    {
        timeout--;
    }
    // 访问SR2，清除ADDR标志位
    if (timeout > 0)
    {
        I2C2->SR2;
    }

    return timeout ? OK : FAIL;
}

// 主机发送一个字节的数据（写入），并等待应答
uint8_t I2C_SendByte(uint8_t byte)
{
    // 1. 先等待DR为空，上一个字节数据已经发送完毕
    uint16_t timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout)
    {
        timeout--;
    }

    // 2. 将要发送的字节放入DR中
    I2C2->DR = byte;

    // 3. 等待应答
    timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout)
    {
        timeout--;
    }
    return timeout ? OK : FAIL;
}

// 主机从EEPROM接收一个字节的数据（读取）
uint8_t I2C_ReadByte(void)
{
    // 1. 先等待DR为满
    uint16_t timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_RXNE) == 0 && timeout)
    {
        timeout--;
    }

    // 2. 将收到的字节数据返回
    return timeout ? I2C2->DR : FAIL;
}
